// file:system/task/fork.c
// time:2021.4.3
// autor:jiangxinpeng
// copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/task.h>
#include <os/vmm.h>
#include <os/fd.h>
#include <os/process.h>
#include <os/schedule.h>
#include <os/pthread.h>
#include <os/debug.h>
#include <arch/task.h>
#include <lib/string.h>
#include <lib/list.h>
#include <lib/assert.h>

static pid_t TaskForkPid()
{
    return TaskTakePid();
}

// copy task kstack and struct info to child
static int CopyKstackAndStruct(task_t *child, task_t *parent)
{
    // copy kstatck
    memcpy(child, parent, TASK_KERNEL_STACK_SIZE);
    // fork pid to child
    child->pid = TaskForkPid();
    child->threadgroup_id = child->pid;
    child->status = TASK_READY;
    child->priority = TASK_PRIOR_LEVEL_NORMAL;
    child->parent_pid = parent->pid;
    child->processgroup_id = parent->processgroup_id;
    list_init(&child->list);
    list_init(&child->global_list);
    child->kstack = ((uint8_t *)child + TASK_KERNEL_STACK_SIZE - sizeof(trap_frame_t));
    child->port_comm = NULL;
    return 0;
}

static void CopyVmStruct(task_t *child, task_t *parent)
{
    child->vmm->code_start = parent->vmm->code_start;
    child->vmm->code_end = parent->vmm->code_end;
    child->vmm->data_start = parent->vmm->data_start;
    child->vmm->data_end = parent->vmm->data_end;
    child->vmm->heap_start = parent->vmm->heap_start;
    child->vmm->heap_end = parent->vmm->heap_end;
    child->vmm->map_start = parent->vmm->map_start;
    child->vmm->map_end = parent->vmm->map_end;
}

static int CopyVm(task_t *child, task_t *parent)
{
    if (ProcessVmmInit(child) < 0)
    {
        KPrint("[fork] init child vmm err\n");
        return -1;
    }
    CopyVmStruct(child, parent);
    if (VmmCopyMapSpace(child->vmm, parent->vmm) < 0)
    {
        KPrint("[fork] copy map space from parent err\n");
        return -1;
    }
    if (VmmCopyMapping(child, parent) < 0)
    {
        KPrint("[fork] copy page mapping err\n");
        return -1;
    }
    return 0;
}

static int CopyException(task_t *child, task_t *parent)
{
    ExceptionManagerInit(&child->exception_manager);
    return ExceptionCopy(&child->exception_manager, &parent->exception_manager);
}

static int CopyFileFd(task_t *child, task_t *parent)
{
    if (FsFdInit(child) < 0)
    {
        KPrint(PRINT_ERR "[fork] %s: child task %s init fileman failed!\n", __func__, child->name);
        return -1;
    }
    return FsFdCopy(child, parent);
}

static int CopyPthreadDescript(task_t *child, task_t *parent)
{
    if (parent->pthread != NULL)
    {
        // user process only one thread
        if (ProcessThreadInit(child))
        {
            return -1;
        }
    }
    return 0;
}

static int CopyTask(task_t *child, task_t *parent)
{
    int err;

    if (CopyKstackAndStruct(child, parent))
    {
        KPrint("[fork] copy task struct err\n");
        goto rollback_failed;
    }
    if (CopyVm(child, parent))
    {
        KPrint("[fork] copy vmm err\n");
        goto rollback_struct;
    }
    if (CopyPthreadDescript(child, parent))
    {
        KPrint("[fork] copy pthread err\n");
        goto rollback_vmm;
    }
    if (CopyFileFd(child, parent))
    {
        KPrint("[fork] copy fd err\n");
        goto rollback_pthread_descript;
    }
    if (CopyException(child, parent))
    {
        KPrint("[fork] copy exception err\n");
        goto rollback_file;
    }
    TaskStackBuildWhenForking(child);
    return 0;

rollback_file:
    FsFdExit(child);
rollback_pthread_descript:
    ProcessThreadExit(child);
rollback_vmm:
    ProcessVmmExitWhenForking(child, parent);
rollback_struct:
    TaskRollBackPid();
rollback_failed:
    return -1;
}

int SysFork()
{
    task_t *parent = cur_task;
    task_t *child = KMemAlloc(TASK_KERNEL_STACK_SIZE);
    if (!child)
    {
        KPrint(PRINT_ERR "SysFork: KMemAlloc for child task failed!\n");
        return -1;
    }

    // parent task must is a process
    assert(parent->vmm);

    if (CopyTask(child, parent))
    {
        KPrint(PRINT_ERR "SysFork: Copy task to child failed!\n");
        KMemFree(child);
        return -1;
    }
    TaskAddToGlobalList(child);
    SchedQueueAddTaskTail(SchedGetCurUnit(), child);
    return child->pid;
}
